/*******************************************************************************
 * Copyright (c) 2008-2009 zhang yuexiang.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ 
package org.xfeep.asura.bootstrap.config;



import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xfeep.asura.core.config.ConfigLoader;
import org.xfeep.asura.core.config.LazyConfigInfo;
import org.xfeep.asura.core.util.FinishedCallable;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

public class XmlConfigLoader implements ConfigLoader {

	static DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	static {
		factory.setNamespaceAware(true);
	}
	static JAXBContext jaxbContext = null;
	protected String configId;
	protected LazyConfigInfo lazyConfigInfo = new LazyConfigInfo();
	protected List<LazyConfigInfo> lazyConfigInfoListForIterator = Arrays.asList(lazyConfigInfo);
	public static JAXBContext getJaxbContext() {
		if (jaxbContext == null){
			try {
				jaxbContext = JAXBContext.newInstance();
			} catch (JAXBException e) {
				throw new RuntimeException("can not create JAXBContext", e);
			}
		}
		return jaxbContext;
	}
	
	public XmlConfigLoader(File file){
		initForLazyLoading(file);
	}
	
	public XmlConfigLoader(String file){
		this(new File(file));
	}
	
	public XmlConfigLoader(String file, boolean lazyLoading) throws ParserConfigurationException, SAXException, IOException{
		this(new File(file), lazyLoading);
	}
	
	
	public XmlConfigLoader(File file, boolean lazyLoading) throws ParserConfigurationException, SAXException, IOException {
		if (!lazyLoading){
			FileInputStream in = new FileInputStream(file);
			try{
				initByStream(this, in);
			}finally{
				in.close();
			}
		}else{
			initForLazyLoading(file);
		}
	}
	
	public XmlConfigLoader(final String configId, InputStream inputStream) throws ParserConfigurationException, SAXException, IOException {
		this.configId = configId;
		ConfigLazyMap cm = initByStream(this, inputStream);
		lazyConfigInfo.setId(configId);
		lazyConfigInfo.setConfigFetcher(new FinishedCallable<Map<String,Object>>(cm));

	}
	
	public XmlConfigLoader(String configId, Node node) {
		this.configId = configId;
		ConfigLazyMap cm =  initByNode(this, node);
		lazyConfigInfo.setId(configId);
		lazyConfigInfo.setConfigFetcher(new FinishedCallable<Map<String,Object>>(cm));
	}
	
	public void initForLazyLoading(File file) {
		 String fileName = file.getName();
		 if (fileName.toLowerCase().endsWith(".xml")){
				this.configId = fileName.substring(0, fileName.length() - 4);
		 }else{
			 throw new IllegalArgumentException(file + " must be xml file with suffix .xml"); // skip non-xml file
		 }
		 final File cfgFile = file;
		 Callable<Map<String, Object>> builder = new Callable<Map<String, Object>>() {
               public ConfigLazyMap call() throws Exception {
               	if (!cfgFile.exists()){
               		return null;
               	}
               	FileInputStream in = null;
               	try {
               		in = new FileInputStream(cfgFile);
               		return initByStream(XmlConfigLoader.this, in);
//               	} catch (Throwable e) {
//           			LogManager.getInstance().warn(e.getMessage(), e);
           		}finally{
           			if (in != null) {
           				in.close();
           			}
           		}
               }
         };
       lazyConfigInfo.setId(configId);
       lazyConfigInfo.setConfigFetcher(builder);
	
	}
	
	public static ConfigLazyMap initByNode(XmlConfigLoader loader, Node node) {
		if (loader.configId == null){
			loader.configId = getAttrValue(node.getAttributes(), "id");
			if (loader.configId == null){
				loader.configId = node.getLocalName();
			}
		}
		Map<String, Node> nodeMap = new HashMap<String, Node>();
		NodeList nl = node.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++){
			Node n = nl.item(i);
			if (n.getLocalName() == null){
				continue;
			}
			nodeMap.put(n.getLocalName(), n);
		}
		ConfigLazyMap cm = new ConfigLazyMap((Map)nodeMap, getJaxbContext());
		cm.setConfigId(loader.configId);
		return cm;
	}
	
	public static ConfigLazyMap initByStream(final XmlConfigLoader loader, InputStream inputStream) throws ParserConfigurationException, SAXException, IOException{
		DocumentBuilder docBuilder =  factory.newDocumentBuilder();
		docBuilder.setErrorHandler(new DefaultHandler() {
			public void error(SAXParseException e) throws SAXException {
				String systemId = e.getSystemId();
				if (systemId == null) {
					systemId = "null";
				}
				String message = "Wrong XML inputStream for : configId=" + loader.configId + " Line="
						+ e.getLineNumber() + ": " + e.getMessage();
				throw new SAXException(message, e);
			}
		});
		return initByNode(loader, docBuilder.parse(inputStream).getDocumentElement());
	}
	
	public XmlConfigLoader() {
	}
	
	
	
	public Iterator<LazyConfigInfo> iterator() {
		return lazyConfigInfoListForIterator.iterator();
	}
	
	public static String getAttrValue(NamedNodeMap attrsMap, String name){
		Node n = attrsMap.getNamedItem(name);
		if (n == null){
			return null;
		}
		return  n.getNodeValue();
	}

}
